/*-------------------------------------------------------------------------
 * drawElements Quality Program Tester Core
 * ----------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Texture compare (shadow) result verifier.
 *//*--------------------------------------------------------------------*/

#include "tcuTexCompareVerifier.hpp"
#include "tcuTexVerifierUtil.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuVectorUtil.hpp"
#include "deMath.h"

namespace tcu
{

using namespace TexVerifierUtil;

// Generic utilities

#if defined(DE_DEBUG)
static bool isSamplerSupported (const Sampler& sampler)
{
	return sampler.compare != Sampler::COMPAREMODE_NONE &&
		   isWrapModeSupported(sampler.wrapS)			&&
		   isWrapModeSupported(sampler.wrapT)			&&
		   isWrapModeSupported(sampler.wrapR);
}
#endif // DE_DEBUG

struct CmpResultSet
{
	bool	isTrue;
	bool	isFalse;

	CmpResultSet (void)
		: isTrue	(false)
		, isFalse	(false)
	{
	}
};

static CmpResultSet execCompare (const Sampler::CompareMode	compareMode,
								 const float				cmpValue_,
								 const float				cmpReference_,
								 const int					referenceBits,
								 const bool					isFixedPoint)
{
	const bool		clampValues		= isFixedPoint;	// if comparing against a floating point texture, ref (and value) is not clamped
	const float		cmpValue		= (clampValues) ? (de::clamp(cmpValue_, 0.0f, 1.0f)) : (cmpValue_);
	const float		cmpReference	= (clampValues) ? (de::clamp(cmpReference_, 0.0f, 1.0f)) : (cmpReference_);
	const float		err				= computeFixedPointError(referenceBits);
	CmpResultSet	res;

	switch (compareMode)
	{
		case Sampler::COMPAREMODE_LESS:
			res.isTrue	= cmpReference-err < cmpValue;
			res.isFalse	= cmpReference+err >= cmpValue;
			break;

		case Sampler::COMPAREMODE_LESS_OR_EQUAL:
			res.isTrue	= cmpReference-err <= cmpValue;
			res.isFalse	= cmpReference+err > cmpValue;
			break;

		case Sampler::COMPAREMODE_GREATER:
			res.isTrue	= cmpReference+err > cmpValue;
			res.isFalse	= cmpReference-err <= cmpValue;
			break;

		case Sampler::COMPAREMODE_GREATER_OR_EQUAL:
			res.isTrue	= cmpReference+err >= cmpValue;
			res.isFalse	= cmpReference-err < cmpValue;
			break;

		case Sampler::COMPAREMODE_EQUAL:
			res.isTrue	= de::inRange(cmpValue, cmpReference-err, cmpReference+err);
			res.isFalse	= err != 0.0f || cmpValue != cmpReference;
			break;

		case Sampler::COMPAREMODE_NOT_EQUAL:
			res.isTrue	= err != 0.0f || cmpValue != cmpReference;
			res.isFalse	= de::inRange(cmpValue, cmpReference-err, cmpReference+err);
			break;

		case Sampler::COMPAREMODE_ALWAYS:
			res.isTrue	= true;
			break;

		case Sampler::COMPAREMODE_NEVER:
			res.isFalse	= true;
			break;

		default:
			DE_ASSERT(false);
	}

	DE_ASSERT(res.isTrue || res.isFalse);
	return res;
}

static inline bool isResultInSet (const CmpResultSet resultSet, const float result, const int resultBits)
{
	const float err		= computeFixedPointError(resultBits);
	const float	minR	= result-err;
	const float	maxR	= result+err;

	return (resultSet.isTrue	&& de::inRange(1.0f, minR, maxR)) ||
		   (resultSet.isFalse	&& de::inRange(0.0f, minR, maxR));
}

// Values are in order (0,0), (1,0), (0,1), (1,1)
static float bilinearInterpolate (const Vec4& values, const float x, const float y)
{
	const float		v00		= values[0];
	const float		v10		= values[1];
	const float		v01		= values[2];
	const float		v11		= values[3];
	const float		res		= v00*(1.0f-x)*(1.0f-y) + v10*x*(1.0f-y) + v01*(1.0f-x)*y + v11*x*y;
	return res;
}

static bool isFixedPointDepthTextureFormat (const tcu::TextureFormat& format)
{
	const tcu::TextureChannelClass channelClass = tcu::getTextureChannelClass(format.type);

	if (format.order == TextureFormat::D)
	{
		// depth internal formats cannot be non-normalized integers
		return channelClass != tcu::TEXTURECHANNELCLASS_FLOATING_POINT;
	}
	else if (format.order == TextureFormat::DS)
	{
		// combined formats have no single channel class, detect format manually
		switch (format.type)
		{
			case tcu::TextureFormat::FLOAT_UNSIGNED_INT_24_8_REV:	return false;
			case tcu::TextureFormat::UNSIGNED_INT_24_8:				return true;

			default:
			{
				// unknown format
				DE_ASSERT(false);
				return true;
			}
		}
	}

	return false;
}

static bool isLinearCompareValid (const Sampler::CompareMode	compareMode,
								  const TexComparePrecision&	prec,
								  const Vec2&					depths,
								  const Vec2&					fBounds,
								  const float					cmpReference,
								  const float					result,
								  const bool					isFixedPointDepth)
{
	DE_ASSERT(0.0f <= fBounds.x() && fBounds.x() <= fBounds.y() && fBounds.y() <= 1.0f);

	const float			d0			= depths[0];
	const float			d1			= depths[1];

	const CmpResultSet	cmp0		= execCompare(compareMode, d0, cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp1		= execCompare(compareMode, d1, cmpReference, prec.referenceBits, isFixedPointDepth);

	const deUint32		isTrue		= (deUint32(cmp0.isTrue)<<0)
									| (deUint32(cmp1.isTrue)<<1);
	const deUint32		isFalse		= (deUint32(cmp0.isFalse)<<0)
									| (deUint32(cmp1.isFalse)<<1);

	// Interpolation parameters
	const float			f0			= fBounds.x();
	const float			f1			= fBounds.y();

	// Error parameters
	const float			pcfErr		= computeFixedPointError(prec.pcfBits);
	const float			resErr		= computeFixedPointError(prec.resultBits);
	const float			totalErr	= pcfErr+resErr;

	// Iterate over all valid combinations.
	for (deUint32 comb = 0; comb < (1<<2); comb++)
	{
		// Filter out invalid combinations.
		if (((comb & isTrue) | (~comb & isFalse)) != (1<<2)-1)
			continue;

		const bool		cmp0True	= ((comb>>0)&1) != 0;
		const bool		cmp1True	= ((comb>>1)&1) != 0;

		const float		ref0		= cmp0True ? 1.0f : 0.0f;
		const float		ref1		= cmp1True ? 1.0f : 0.0f;

		const float		v0			= ref0*(1.0f-f0) + ref1*f0;
		const float		v1			= ref0*(1.0f-f1) + ref1*f1;
		const float		minV		= de::min(v0, v1);
		const float		maxV		= de::max(v0, v1);
		const float		minR		= minV-totalErr;
		const float		maxR		= maxV+totalErr;

		if (de::inRange(result, minR, maxR))
			return true;
	}

	return false;
}

static inline BVec4 extractBVec4 (const deUint32 val, int offset)
{
	return BVec4(((val>>(offset+0))&1) != 0,
				 ((val>>(offset+1))&1) != 0,
				 ((val>>(offset+2))&1) != 0,
				 ((val>>(offset+3))&1) != 0);
}

static bool isBilinearAnyCompareValid (const Sampler::CompareMode	compareMode,
									   const TexComparePrecision&	prec,
									   const Vec4&					depths,
									   const float					cmpReference,
									   const float					result,
									   const bool					isFixedPointDepth)
{
	DE_ASSERT(prec.pcfBits == 0);

	const float			d0			= depths[0];
	const float			d1			= depths[1];
	const float			d2			= depths[2];
	const float			d3			= depths[3];

	const CmpResultSet	cmp0		= execCompare(compareMode, d0, cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp1		= execCompare(compareMode, d1, cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp2		= execCompare(compareMode, d2, cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp3		= execCompare(compareMode, d3, cmpReference, prec.referenceBits, isFixedPointDepth);

	const bool			canBeTrue	= cmp0.isTrue || cmp1.isTrue || cmp2.isTrue || cmp3.isTrue;
	const bool			canBeFalse	= cmp0.isFalse || cmp1.isFalse || cmp2.isFalse || cmp3.isFalse;

	const float			resErr		= computeFixedPointError(prec.resultBits);

	const float			minBound	= canBeFalse ? 0.0f : 1.0f;
	const float			maxBound	= canBeTrue ? 1.0f : 0.0f;

	return de::inRange(result, minBound-resErr, maxBound+resErr);
}

static bool isBilinearPCFCompareValid (const Sampler::CompareMode	compareMode,
									   const TexComparePrecision&	prec,
									   const Vec4&					depths,
									   const Vec2&					xBounds,
									   const Vec2&					yBounds,
									   const float					cmpReference,
									   const float					result,
									   const bool					isFixedPointDepth)
{
	DE_ASSERT(0.0f <= xBounds.x() && xBounds.x() <= xBounds.y() && xBounds.y() <= 1.0f);
	DE_ASSERT(0.0f <= yBounds.x() && yBounds.x() <= yBounds.y() && yBounds.y() <= 1.0f);
	DE_ASSERT(prec.pcfBits > 0);

	const float			d0			= depths[0];
	const float			d1			= depths[1];
	const float			d2			= depths[2];
	const float			d3			= depths[3];

	const CmpResultSet	cmp0		= execCompare(compareMode, d0, cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp1		= execCompare(compareMode, d1, cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp2		= execCompare(compareMode, d2, cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp3		= execCompare(compareMode, d3, cmpReference, prec.referenceBits, isFixedPointDepth);

	const deUint32		isTrue		= (deUint32(cmp0.isTrue)<<0)
									| (deUint32(cmp1.isTrue)<<1)
									| (deUint32(cmp2.isTrue)<<2)
									| (deUint32(cmp3.isTrue)<<3);
	const deUint32		isFalse		= (deUint32(cmp0.isFalse)<<0)
									| (deUint32(cmp1.isFalse)<<1)
									| (deUint32(cmp2.isFalse)<<2)
									| (deUint32(cmp3.isFalse)<<3);

	// Interpolation parameters
	const float			x0			= xBounds.x();
	const float			x1			= xBounds.y();
	const float			y0			= yBounds.x();
	const float			y1			= yBounds.y();

	// Error parameters
	const float			pcfErr		= computeFixedPointError(prec.pcfBits);
	const float			resErr		= computeFixedPointError(prec.resultBits);
	const float			totalErr	= pcfErr+resErr;

	// Iterate over all valid combinations.
	// \note It is not enough to compute minmax over all possible result sets, as ranges may
	//		 not necessarily overlap, i.e. there are gaps between valid ranges.
	for (deUint32 comb = 0; comb < (1<<4); comb++)
	{
		// Filter out invalid combinations:
		//  1) True bit is set in comb but not in isTrue => sample can not be true
		//  2) True bit is NOT set in comb and not in isFalse => sample can not be false
		if (((comb & isTrue) | (~comb & isFalse)) != (1<<4)-1)
			continue;

		const BVec4		cmpTrue		= extractBVec4(comb, 0);
		const Vec4		refVal		= select(Vec4(1.0f), Vec4(0.0f), cmpTrue);

		const float		v0			= bilinearInterpolate(refVal, x0, y0);
		const float		v1			= bilinearInterpolate(refVal, x1, y0);
		const float		v2			= bilinearInterpolate(refVal, x0, y1);
		const float		v3			= bilinearInterpolate(refVal, x1, y1);
		const float		minV		= de::min(v0, de::min(v1, de::min(v2, v3)));
		const float		maxV		= de::max(v0, de::max(v1, de::max(v2, v3)));
		const float		minR		= minV-totalErr;
		const float		maxR		= maxV+totalErr;

		if (de::inRange(result, minR, maxR))
			return true;
	}

	return false;
}

static bool isBilinearCompareValid (const Sampler::CompareMode	compareMode,
									const TexComparePrecision&	prec,
									const Vec4&					depths,
									const Vec2&					xBounds,
									const Vec2&					yBounds,
									const float					cmpReference,
									const float					result,
									const bool					isFixedPointDepth)
{
	if (prec.pcfBits > 0)
		return isBilinearPCFCompareValid(compareMode, prec, depths, xBounds, yBounds, cmpReference, result, isFixedPointDepth);
	else
		return isBilinearAnyCompareValid(compareMode, prec, depths, cmpReference, result, isFixedPointDepth);
}

static bool isTrilinearAnyCompareValid (const Sampler::CompareMode	compareMode,
										const TexComparePrecision&	prec,
										const Vec4&					depths0,
										const Vec4&					depths1,
										const float					cmpReference,
										const float					result,
										const bool					isFixedPointDepth)
{
	DE_ASSERT(prec.pcfBits == 0);

	const CmpResultSet	cmp00		= execCompare(compareMode, depths0[0], cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp01		= execCompare(compareMode, depths0[1], cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp02		= execCompare(compareMode, depths0[2], cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp03		= execCompare(compareMode, depths0[3], cmpReference, prec.referenceBits, isFixedPointDepth);

	const CmpResultSet	cmp10		= execCompare(compareMode, depths1[0], cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp11		= execCompare(compareMode, depths1[1], cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp12		= execCompare(compareMode, depths1[2], cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp13		= execCompare(compareMode, depths1[3], cmpReference, prec.referenceBits, isFixedPointDepth);

	const bool			canBeTrue	= cmp00.isTrue ||
									  cmp01.isTrue ||
									  cmp02.isTrue ||
									  cmp03.isTrue ||
									  cmp10.isTrue ||
									  cmp11.isTrue ||
									  cmp12.isTrue ||
									  cmp13.isTrue;
	const bool			canBeFalse	= cmp00.isFalse ||
									  cmp01.isFalse ||
									  cmp02.isFalse ||
									  cmp03.isFalse ||
									  cmp10.isFalse ||
									  cmp11.isFalse ||
									  cmp12.isFalse ||
									  cmp13.isFalse;

	const float			resErr		= computeFixedPointError(prec.resultBits);

	const float			minBound	= canBeFalse ? 0.0f : 1.0f;
	const float			maxBound	= canBeTrue ? 1.0f : 0.0f;

	return de::inRange(result, minBound-resErr, maxBound+resErr);
}

static bool isTrilinearPCFCompareValid (const Sampler::CompareMode	compareMode,
										const TexComparePrecision&	prec,
										const Vec4&					depths0,
										const Vec4&					depths1,
										const Vec2&					xBounds0,
										const Vec2&					yBounds0,
										const Vec2&					xBounds1,
										const Vec2&					yBounds1,
										const Vec2&					fBounds,
										const float					cmpReference,
										const float					result,
										const bool					isFixedPointDepth)
{
	DE_ASSERT(0.0f <= xBounds0.x() && xBounds0.x() <= xBounds0.y() && xBounds0.y() <= 1.0f);
	DE_ASSERT(0.0f <= yBounds0.x() && yBounds0.x() <= yBounds0.y() && yBounds0.y() <= 1.0f);
	DE_ASSERT(0.0f <= xBounds1.x() && xBounds1.x() <= xBounds1.y() && xBounds1.y() <= 1.0f);
	DE_ASSERT(0.0f <= yBounds1.x() && yBounds1.x() <= yBounds1.y() && yBounds1.y() <= 1.0f);
	DE_ASSERT(0.0f <= fBounds.x() && fBounds.x() <= fBounds.y() && fBounds.y() <= 1.0f);
	DE_ASSERT(prec.pcfBits > 0);

	const CmpResultSet	cmp00		= execCompare(compareMode, depths0[0], cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp01		= execCompare(compareMode, depths0[1], cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp02		= execCompare(compareMode, depths0[2], cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp03		= execCompare(compareMode, depths0[3], cmpReference, prec.referenceBits, isFixedPointDepth);

	const CmpResultSet	cmp10		= execCompare(compareMode, depths1[0], cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp11		= execCompare(compareMode, depths1[1], cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp12		= execCompare(compareMode, depths1[2], cmpReference, prec.referenceBits, isFixedPointDepth);
	const CmpResultSet	cmp13		= execCompare(compareMode, depths1[3], cmpReference, prec.referenceBits, isFixedPointDepth);

	const deUint32		isTrue		= (deUint32(cmp00.isTrue)<<0)
									| (deUint32(cmp01.isTrue)<<1)
									| (deUint32(cmp02.isTrue)<<2)
									| (deUint32(cmp03.isTrue)<<3)
									| (deUint32(cmp10.isTrue)<<4)
									| (deUint32(cmp11.isTrue)<<5)
									| (deUint32(cmp12.isTrue)<<6)
									| (deUint32(cmp13.isTrue)<<7);
	const deUint32		isFalse		= (deUint32(cmp00.isFalse)<<0)
									| (deUint32(cmp01.isFalse)<<1)
									| (deUint32(cmp02.isFalse)<<2)
									| (deUint32(cmp03.isFalse)<<3)
									| (deUint32(cmp10.isFalse)<<4)
									| (deUint32(cmp11.isFalse)<<5)
									| (deUint32(cmp12.isFalse)<<6)
									| (deUint32(cmp13.isFalse)<<7);

	// Error parameters
	const float			pcfErr		= computeFixedPointError(prec.pcfBits);
	const float			resErr		= computeFixedPointError(prec.resultBits);
	const float			totalErr	= pcfErr+resErr;

	// Iterate over all valid combinations.
	for (deUint32 comb = 0; comb < (1<<8); comb++)
	{
		// Filter out invalid combinations.
		if (((comb & isTrue) | (~comb & isFalse)) != (1<<8)-1)
			continue;

		const BVec4		cmpTrue0	= extractBVec4(comb, 0);
		const BVec4		cmpTrue1	= extractBVec4(comb, 4);
		const Vec4		refVal0		= select(Vec4(1.0f), Vec4(0.0f), cmpTrue0);
		const Vec4		refVal1		= select(Vec4(1.0f), Vec4(0.0f), cmpTrue1);

		// Bilinear interpolation within levels.
		const float		v00			= bilinearInterpolate(refVal0, xBounds0.x(), yBounds0.x());
		const float		v01			= bilinearInterpolate(refVal0, xBounds0.y(), yBounds0.x());
		const float		v02			= bilinearInterpolate(refVal0, xBounds0.x(), yBounds0.y());
		const float		v03			= bilinearInterpolate(refVal0, xBounds0.y(), yBounds0.y());
		const float		minV0		= de::min(v00, de::min(v01, de::min(v02, v03)));
		const float		maxV0		= de::max(v00, de::max(v01, de::max(v02, v03)));

		const float		v10			= bilinearInterpolate(refVal1, xBounds1.x(), yBounds1.x());
		const float		v11			= bilinearInterpolate(refVal1, xBounds1.y(), yBounds1.x());
		const float		v12			= bilinearInterpolate(refVal1, xBounds1.x(), yBounds1.y());
		const float		v13			= bilinearInterpolate(refVal1, xBounds1.y(), yBounds1.y());
		const float		minV1		= de::min(v10, de::min(v11, de::min(v12, v13)));
		const float		maxV1		= de::max(v10, de::max(v11, de::max(v12, v13)));

		// Compute min-max bounds by filtering between minimum bounds and maximum bounds between levels.
		// HW can end up choosing pretty much any of samples between levels, and thus interpolating
		// between minimums should yield lower bound for range, and same for upper bound.
		// \todo [2013-07-17 pyry] This seems separable? Can this be optimized? At least ranges could be pre-computed and later combined.
		const float		minF0		= minV0*(1.0f-fBounds.x()) + minV1*fBounds.x();
		const float		minF1		= minV0*(1.0f-fBounds.y()) + minV1*fBounds.y();
		const float		maxF0		= maxV0*(1.0f-fBounds.x()) + maxV1*fBounds.x();
		const float		maxF1		= maxV0*(1.0f-fBounds.y()) + maxV1*fBounds.y();

		const float		minF		= de::min(minF0, minF1);
		const float		maxF		= de::max(maxF0, maxF1);

		const float		minR		= minF-totalErr;
		const float		maxR		= maxF+totalErr;

		if (de::inRange(result, minR, maxR))
			return true;
	}

	return false;
}

static bool isTrilinearCompareValid (const Sampler::CompareMode	compareMode,
									 const TexComparePrecision&	prec,
									 const Vec4&				depths0,
									 const Vec4&				depths1,
									 const Vec2&				xBounds0,
									 const Vec2&				yBounds0,
									 const Vec2&				xBounds1,
									 const Vec2&				yBounds1,
									 const Vec2&				fBounds,
									 const float				cmpReference,
									 const float				result,
									 const bool					isFixedPointDepth)
{
	if (prec.pcfBits > 0)
		return isTrilinearPCFCompareValid(compareMode, prec, depths0, depths1, xBounds0, yBounds0, xBounds1, yBounds1, fBounds, cmpReference, result, isFixedPointDepth);
	else
		return isTrilinearAnyCompareValid(compareMode, prec, depths0, depths1, cmpReference, result, isFixedPointDepth);
}

static bool isNearestCompareResultValid (const ConstPixelBufferAccess&		level,
										 const Sampler&						sampler,
										 const TexComparePrecision&			prec,
										 const Vec2&						coord,
										 const int							coordZ,
										 const float						cmpReference,
										 const float						result)
{
	const bool	isFixedPointDepth	= isFixedPointDepthTextureFormat(level.getFormat());
	const Vec2	uBounds				= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getWidth(),	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2	vBounds				= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getHeight(),	coord.y(), prec.coordBits.y(), prec.uvwBits.y());

	// Integer coordinates - without wrap mode
	const int	minI		= deFloorFloatToInt32(uBounds.x());
	const int	maxI		= deFloorFloatToInt32(uBounds.y());
	const int	minJ		= deFloorFloatToInt32(vBounds.x());
	const int	maxJ		= deFloorFloatToInt32(vBounds.y());

	for (int j = minJ; j <= maxJ; j++)
	{
		for (int i = minI; i <= maxI; i++)
		{
			const int			x		= wrap(sampler.wrapS, i, level.getWidth());
			const int			y		= wrap(sampler.wrapT, j, level.getHeight());
			const float			depth	= level.getPixDepth(x, y, coordZ);
			const CmpResultSet	resSet	= execCompare(sampler.compare, depth, cmpReference, prec.referenceBits, isFixedPointDepth);

			if (isResultInSet(resSet, result, prec.resultBits))
				return true;
		}
	}

	return false;
}

static bool isLinearCompareResultValid (const ConstPixelBufferAccess&		level,
										const Sampler&						sampler,
										const TexComparePrecision&			prec,
										const Vec2&							coord,
										const int							coordZ,
										const float							cmpReference,
										const float							result)
{
	const bool	isFixedPointDepth	= isFixedPointDepthTextureFormat(level.getFormat());
	const Vec2	uBounds				= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getWidth(),	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2	vBounds				= computeNonNormalizedCoordBounds(sampler.normalizedCoords, level.getHeight(),	coord.y(), prec.coordBits.y(), prec.uvwBits.y());

	// Integer coordinate bounds for (x0,y0) - without wrap mode
	const int	minI		= deFloorFloatToInt32(uBounds.x()-0.5f);
	const int	maxI		= deFloorFloatToInt32(uBounds.y()-0.5f);
	const int	minJ		= deFloorFloatToInt32(vBounds.x()-0.5f);
	const int	maxJ		= deFloorFloatToInt32(vBounds.y()-0.5f);

	const int	w			= level.getWidth();
	const int	h			= level.getHeight();

	// \todo [2013-07-03 pyry] This could be optimized by first computing ranges based on wrap mode.

	for (int j = minJ; j <= maxJ; j++)
	{
		for (int i = minI; i <= maxI; i++)
		{
			// Wrapped coordinates
			const int	x0		= wrap(sampler.wrapS, i  , w);
			const int	x1		= wrap(sampler.wrapS, i+1, w);
			const int	y0		= wrap(sampler.wrapT, j  , h);
			const int	y1		= wrap(sampler.wrapT, j+1, h);

			// Bounds for filtering factors
			const float	minA	= de::clamp((uBounds.x()-0.5f)-float(i), 0.0f, 1.0f);
			const float	maxA	= de::clamp((uBounds.y()-0.5f)-float(i), 0.0f, 1.0f);
			const float	minB	= de::clamp((vBounds.x()-0.5f)-float(j), 0.0f, 1.0f);
			const float	maxB	= de::clamp((vBounds.y()-0.5f)-float(j), 0.0f, 1.0f);

			const Vec4	depths	(level.getPixDepth(x0, y0, coordZ),
								 level.getPixDepth(x1, y0, coordZ),
								 level.getPixDepth(x0, y1, coordZ),
								 level.getPixDepth(x1, y1, coordZ));

			if (isBilinearCompareValid(sampler.compare, prec, depths, Vec2(minA, maxA), Vec2(minB, maxB), cmpReference, result, isFixedPointDepth))
				return true;
		}
	}

	return false;
}

static bool isLevelCompareResultValid (const ConstPixelBufferAccess&	level,
									   const Sampler&					sampler,
									   const Sampler::FilterMode		filterMode,
									   const TexComparePrecision&		prec,
									   const Vec2&						coord,
									   const int						coordZ,
									   const float						cmpReference,
									   const float						result)
{
	if (filterMode == Sampler::LINEAR)
		return isLinearCompareResultValid(level, sampler, prec, coord, coordZ, cmpReference, result);
	else
		return isNearestCompareResultValid(level, sampler, prec, coord, coordZ, cmpReference, result);
}

static bool isNearestMipmapLinearCompareResultValid (const ConstPixelBufferAccess&	level0,
													 const ConstPixelBufferAccess&	level1,
													 const Sampler&					sampler,
													 const TexComparePrecision&		prec,
													 const Vec2&					coord,
													 const int						coordZ,
													 const Vec2&					fBounds,
													 const float					cmpReference,
													 const float					result)
{
	const bool	isFixedPointDepth	= isFixedPointDepthTextureFormat(level0.getFormat());

	const int	w0					= level0.getWidth();
	const int	w1					= level1.getWidth();
	const int	h0					= level0.getHeight();
	const int	h1					= level1.getHeight();

	const Vec2	uBounds0			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w0,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2	uBounds1			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w1,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2	vBounds0			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h0,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
	const Vec2	vBounds1			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h1,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());

	// Integer coordinates - without wrap mode
	const int	minI0				= deFloorFloatToInt32(uBounds0.x());
	const int	maxI0				= deFloorFloatToInt32(uBounds0.y());
	const int	minI1				= deFloorFloatToInt32(uBounds1.x());
	const int	maxI1				= deFloorFloatToInt32(uBounds1.y());
	const int	minJ0				= deFloorFloatToInt32(vBounds0.x());
	const int	maxJ0				= deFloorFloatToInt32(vBounds0.y());
	const int	minJ1				= deFloorFloatToInt32(vBounds1.x());
	const int	maxJ1				= deFloorFloatToInt32(vBounds1.y());

	for (int j0 = minJ0; j0 <= maxJ0; j0++)
	{
		for (int i0 = minI0; i0 <= maxI0; i0++)
		{
			const float	depth0	= level0.getPixDepth(wrap(sampler.wrapS, i0, w0), wrap(sampler.wrapT, j0, h0), coordZ);

			for (int j1 = minJ1; j1 <= maxJ1; j1++)
			{
				for (int i1 = minI1; i1 <= maxI1; i1++)
				{
					const float	depth1	= level1.getPixDepth(wrap(sampler.wrapS, i1, w1), wrap(sampler.wrapT, j1, h1), coordZ);

					if (isLinearCompareValid(sampler.compare, prec, Vec2(depth0, depth1), fBounds, cmpReference, result, isFixedPointDepth))
						return true;
				}
			}
		}
	}

	return false;
}

static bool isLinearMipmapLinearCompareResultValid (const ConstPixelBufferAccess&	level0,
													const ConstPixelBufferAccess&	level1,
													const Sampler&					sampler,
													const TexComparePrecision&		prec,
													const Vec2&						coord,
													const int						coordZ,
													const Vec2&						fBounds,
													const float						cmpReference,
													const float						result)
{
	const bool	isFixedPointDepth	= isFixedPointDepthTextureFormat(level0.getFormat());

	// \todo [2013-07-04 pyry] This is strictly not correct as coordinates between levels should be dependent.
	//						   Right now this allows pairing any two valid bilinear quads.

	const int	w0					= level0.getWidth();
	const int	w1					= level1.getWidth();
	const int	h0					= level0.getHeight();
	const int	h1					= level1.getHeight();

	const Vec2	uBounds0			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w0,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2	uBounds1			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, w1,	coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2	vBounds0			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h0,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());
	const Vec2	vBounds1			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, h1,	coord.y(), prec.coordBits.y(), prec.uvwBits.y());

	// Integer coordinates - without wrap mode
	const int	minI0				= deFloorFloatToInt32(uBounds0.x()-0.5f);
	const int	maxI0				= deFloorFloatToInt32(uBounds0.y()-0.5f);
	const int	minI1				= deFloorFloatToInt32(uBounds1.x()-0.5f);
	const int	maxI1				= deFloorFloatToInt32(uBounds1.y()-0.5f);
	const int	minJ0				= deFloorFloatToInt32(vBounds0.x()-0.5f);
	const int	maxJ0				= deFloorFloatToInt32(vBounds0.y()-0.5f);
	const int	minJ1				= deFloorFloatToInt32(vBounds1.x()-0.5f);
	const int	maxJ1				= deFloorFloatToInt32(vBounds1.y()-0.5f);

	for (int j0 = minJ0; j0 <= maxJ0; j0++)
	{
		for (int i0 = minI0; i0 <= maxI0; i0++)
		{
			const float	minA0	= de::clamp((uBounds0.x()-0.5f)-float(i0), 0.0f, 1.0f);
			const float	maxA0	= de::clamp((uBounds0.y()-0.5f)-float(i0), 0.0f, 1.0f);
			const float	minB0	= de::clamp((vBounds0.x()-0.5f)-float(j0), 0.0f, 1.0f);
			const float	maxB0	= de::clamp((vBounds0.y()-0.5f)-float(j0), 0.0f, 1.0f);
			Vec4		depths0;

			{
				const int	x0		= wrap(sampler.wrapS, i0  , w0);
				const int	x1		= wrap(sampler.wrapS, i0+1, w0);
				const int	y0		= wrap(sampler.wrapT, j0  , h0);
				const int	y1		= wrap(sampler.wrapT, j0+1, h0);

				depths0[0] = level0.getPixDepth(x0, y0, coordZ);
				depths0[1] = level0.getPixDepth(x1, y0, coordZ);
				depths0[2] = level0.getPixDepth(x0, y1, coordZ);
				depths0[3] = level0.getPixDepth(x1, y1, coordZ);
			}

			for (int j1 = minJ1; j1 <= maxJ1; j1++)
			{
				for (int i1 = minI1; i1 <= maxI1; i1++)
				{
					const float	minA1	= de::clamp((uBounds1.x()-0.5f)-float(i1), 0.0f, 1.0f);
					const float	maxA1	= de::clamp((uBounds1.y()-0.5f)-float(i1), 0.0f, 1.0f);
					const float	minB1	= de::clamp((vBounds1.x()-0.5f)-float(j1), 0.0f, 1.0f);
					const float	maxB1	= de::clamp((vBounds1.y()-0.5f)-float(j1), 0.0f, 1.0f);
					Vec4		depths1;

					{
						const int	x0		= wrap(sampler.wrapS, i1  , w1);
						const int	x1		= wrap(sampler.wrapS, i1+1, w1);
						const int	y0		= wrap(sampler.wrapT, j1  , h1);
						const int	y1		= wrap(sampler.wrapT, j1+1, h1);

						depths1[0] = level1.getPixDepth(x0, y0, coordZ);
						depths1[1] = level1.getPixDepth(x1, y0, coordZ);
						depths1[2] = level1.getPixDepth(x0, y1, coordZ);
						depths1[3] = level1.getPixDepth(x1, y1, coordZ);
					}

					if (isTrilinearCompareValid(sampler.compare, prec, depths0, depths1,
												Vec2(minA0, maxA0), Vec2(minB0, maxB0),
												Vec2(minA1, maxA1), Vec2(minB1, maxB1),
												fBounds, cmpReference, result, isFixedPointDepth))
						return true;
				}
			}
		}
	}

	return false;
}

static bool isMipmapLinearCompareResultValid (const ConstPixelBufferAccess&		level0,
											  const ConstPixelBufferAccess&		level1,
											  const Sampler&					sampler,
											  const Sampler::FilterMode			levelFilter,
											  const TexComparePrecision&		prec,
											  const Vec2&						coord,
											  const int							coordZ,
											  const Vec2&						fBounds,
											  const float						cmpReference,
											  const float						result)
{
	if (levelFilter == Sampler::LINEAR)
		return isLinearMipmapLinearCompareResultValid(level0, level1, sampler, prec, coord, coordZ, fBounds, cmpReference, result);
	else
		return isNearestMipmapLinearCompareResultValid(level0, level1, sampler, prec, coord, coordZ, fBounds, cmpReference, result);
}

bool isTexCompareResultValid (const Texture2DView&			texture,
							  const Sampler&				sampler,
							  const TexComparePrecision&	prec,
							  const Vec2&					coord,
							  const Vec2&					lodBounds,
							  const float					cmpReference,
							  const float					result)
{
	const float		minLod			= lodBounds.x();
	const float		maxLod			= lodBounds.y();
	const bool		canBeMagnified	= minLod <= sampler.lodThreshold;
	const bool		canBeMinified	= maxLod > sampler.lodThreshold;

	DE_ASSERT(isSamplerSupported(sampler));

	if (canBeMagnified)
	{
		if (isLevelCompareResultValid(texture.getLevel(0), sampler, sampler.magFilter, prec, coord, 0, cmpReference, result))
			return true;
	}

	if (canBeMinified)
	{
		const bool	isNearestMipmap	= isNearestMipmapFilter(sampler.minFilter);
		const bool	isLinearMipmap	= isLinearMipmapFilter(sampler.minFilter);
		const int	minTexLevel		= 0;
		const int	maxTexLevel		= texture.getNumLevels()-1;

		DE_ASSERT(minTexLevel < maxTexLevel);

		if (isLinearMipmap)
		{
			const int		minLevel		= de::clamp((int)deFloatFloor(minLod), minTexLevel, maxTexLevel-1);
			const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod), minTexLevel, maxTexLevel-1);

			DE_ASSERT(minLevel <= maxLevel);

			for (int level = minLevel; level <= maxLevel; level++)
			{
				const float		minF	= de::clamp(minLod - float(level), 0.0f, 1.0f);
				const float		maxF	= de::clamp(maxLod - float(level), 0.0f, 1.0f);

				if (isMipmapLinearCompareResultValid(texture.getLevel(level), texture.getLevel(level+1), sampler, getLevelFilter(sampler.minFilter), prec, coord, 0, Vec2(minF, maxF), cmpReference, result))
					return true;
			}
		}
		else if (isNearestMipmap)
		{
			// \note The accurate formula for nearest mipmapping is level = ceil(lod + 0.5) - 1 but Khronos has made
			//		 decision to allow floor(lod + 0.5) as well.
			const int		minLevel		= de::clamp((int)deFloatCeil(minLod + 0.5f) - 1,	minTexLevel, maxTexLevel);
			const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod + 0.5f),		minTexLevel, maxTexLevel);

			DE_ASSERT(minLevel <= maxLevel);

			for (int level = minLevel; level <= maxLevel; level++)
			{
				if (isLevelCompareResultValid(texture.getLevel(level), sampler, getLevelFilter(sampler.minFilter), prec, coord, 0, cmpReference, result))
					return true;
			}
		}
		else
		{
			if (isLevelCompareResultValid(texture.getLevel(0), sampler, sampler.minFilter, prec, coord, 0, cmpReference, result))
				return true;
		}
	}

	return false;
}

static bool isSeamplessLinearMipmapLinearCompareResultValid (const TextureCubeView&			texture,
															 const int						baseLevelNdx,
															 const Sampler&					sampler,
															 const TexComparePrecision&		prec,
															 const CubeFaceFloatCoords&		coords,
															 const Vec2&					fBounds,
															 const float					cmpReference,
															 const float					result)
{
	const bool	isFixedPointDepth	= isFixedPointDepthTextureFormat(texture.getLevelFace(baseLevelNdx, CUBEFACE_NEGATIVE_X).getFormat());
	const int	size0				= texture.getLevelFace(baseLevelNdx,	coords.face).getWidth();
	const int	size1				= texture.getLevelFace(baseLevelNdx+1,	coords.face).getWidth();

	const Vec2	uBounds0			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size0,	coords.s, prec.coordBits.x(), prec.uvwBits.x());
	const Vec2	uBounds1			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size1,	coords.s, prec.coordBits.x(), prec.uvwBits.x());
	const Vec2	vBounds0			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size0,	coords.t, prec.coordBits.y(), prec.uvwBits.y());
	const Vec2	vBounds1			= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size1,	coords.t, prec.coordBits.y(), prec.uvwBits.y());

	// Integer coordinates - without wrap mode
	const int	minI0				= deFloorFloatToInt32(uBounds0.x()-0.5f);
	const int	maxI0				= deFloorFloatToInt32(uBounds0.y()-0.5f);
	const int	minI1				= deFloorFloatToInt32(uBounds1.x()-0.5f);
	const int	maxI1				= deFloorFloatToInt32(uBounds1.y()-0.5f);
	const int	minJ0				= deFloorFloatToInt32(vBounds0.x()-0.5f);
	const int	maxJ0				= deFloorFloatToInt32(vBounds0.y()-0.5f);
	const int	minJ1				= deFloorFloatToInt32(vBounds1.x()-0.5f);
	const int	maxJ1				= deFloorFloatToInt32(vBounds1.y()-0.5f);

	tcu::ConstPixelBufferAccess faces0[CUBEFACE_LAST];
	tcu::ConstPixelBufferAccess faces1[CUBEFACE_LAST];

	for (int face = 0; face < CUBEFACE_LAST; face++)
	{
		faces0[face] = texture.getLevelFace(baseLevelNdx,	CubeFace(face));
		faces1[face] = texture.getLevelFace(baseLevelNdx+1,	CubeFace(face));
	}

	for (int j0 = minJ0; j0 <= maxJ0; j0++)
	{
		for (int i0 = minI0; i0 <= maxI0; i0++)
		{
			const float	minA0	= de::clamp((uBounds0.x()-0.5f)-float(i0), 0.0f, 1.0f);
			const float	maxA0	= de::clamp((uBounds0.y()-0.5f)-float(i0), 0.0f, 1.0f);
			const float	minB0	= de::clamp((vBounds0.x()-0.5f)-float(j0), 0.0f, 1.0f);
			const float	maxB0	= de::clamp((vBounds0.y()-0.5f)-float(j0), 0.0f, 1.0f);
			Vec4		depths0;

			{
				const CubeFaceIntCoords	c00	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i0+0, j0+0)), size0);
				const CubeFaceIntCoords	c10	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i0+1, j0+0)), size0);
				const CubeFaceIntCoords	c01	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i0+0, j0+1)), size0);
				const CubeFaceIntCoords	c11	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i0+1, j0+1)), size0);

				// If any of samples is out of both edges, implementations can do pretty much anything according to spec.
				// \todo [2013-07-08 pyry] Test the special case where all corner pixels have exactly the same color.
				if (c00.face == CUBEFACE_LAST || c01.face == CUBEFACE_LAST || c10.face == CUBEFACE_LAST || c11.face == CUBEFACE_LAST)
					return true;

				depths0[0] = faces0[c00.face].getPixDepth(c00.s, c00.t);
				depths0[1] = faces0[c10.face].getPixDepth(c10.s, c10.t);
				depths0[2] = faces0[c01.face].getPixDepth(c01.s, c01.t);
				depths0[3] = faces0[c11.face].getPixDepth(c11.s, c11.t);
			}

			for (int j1 = minJ1; j1 <= maxJ1; j1++)
			{
				for (int i1 = minI1; i1 <= maxI1; i1++)
				{
					const float	minA1	= de::clamp((uBounds1.x()-0.5f)-float(i1), 0.0f, 1.0f);
					const float	maxA1	= de::clamp((uBounds1.y()-0.5f)-float(i1), 0.0f, 1.0f);
					const float	minB1	= de::clamp((vBounds1.x()-0.5f)-float(j1), 0.0f, 1.0f);
					const float	maxB1	= de::clamp((vBounds1.y()-0.5f)-float(j1), 0.0f, 1.0f);
					Vec4		depths1;

					{
						const CubeFaceIntCoords	c00	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i1+0, j1+0)), size1);
						const CubeFaceIntCoords	c10	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i1+1, j1+0)), size1);
						const CubeFaceIntCoords	c01	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i1+0, j1+1)), size1);
						const CubeFaceIntCoords	c11	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i1+1, j1+1)), size1);

						if (c00.face == CUBEFACE_LAST || c01.face == CUBEFACE_LAST || c10.face == CUBEFACE_LAST || c11.face == CUBEFACE_LAST)
							return true;

						depths1[0] = faces1[c00.face].getPixDepth(c00.s, c00.t);
						depths1[1] = faces1[c10.face].getPixDepth(c10.s, c10.t);
						depths1[2] = faces1[c01.face].getPixDepth(c01.s, c01.t);
						depths1[3] = faces1[c11.face].getPixDepth(c11.s, c11.t);
					}


					if (isTrilinearCompareValid(sampler.compare, prec, depths0, depths1,
												Vec2(minA0, maxA0), Vec2(minB0, maxB0),
												Vec2(minA1, maxA1), Vec2(minB1, maxB1),
												fBounds, cmpReference, result, isFixedPointDepth))
						return true;
				}
			}
		}
	}

	return false;
}

static bool isCubeMipmapLinearCompareResultValid (const TextureCubeView&		texture,
												  const int						baseLevelNdx,
												  const Sampler&				sampler,
												  const Sampler::FilterMode		levelFilter,
												  const TexComparePrecision&	prec,
												  const CubeFaceFloatCoords&	coords,
												  const Vec2&					fBounds,
												  const float					cmpReference,
												  const float					result)
{
	if (levelFilter == Sampler::LINEAR)
	{
		if (sampler.seamlessCubeMap)
			return isSeamplessLinearMipmapLinearCompareResultValid(texture, baseLevelNdx, sampler, prec, coords, fBounds, cmpReference, result);
		else
			return isLinearMipmapLinearCompareResultValid(texture.getLevelFace(baseLevelNdx,	coords.face),
														  texture.getLevelFace(baseLevelNdx+1,	coords.face),
														  sampler, prec, Vec2(coords.s, coords.t), 0, fBounds, cmpReference, result);
	}
	else
		return isNearestMipmapLinearCompareResultValid(texture.getLevelFace(baseLevelNdx,	coords.face),
													   texture.getLevelFace(baseLevelNdx+1,	coords.face),
													   sampler, prec, Vec2(coords.s, coords.t), 0, fBounds, cmpReference, result);
}

static bool isSeamlessLinearCompareResultValid (const TextureCubeView&		texture,
												const int					levelNdx,
												const Sampler&				sampler,
												const TexComparePrecision&	prec,
												const CubeFaceFloatCoords&	coords,
												const float					cmpReference,
												const float					result)
{
	const bool	isFixedPointDepth	= isFixedPointDepthTextureFormat(texture.getLevelFace(levelNdx, CUBEFACE_NEGATIVE_X).getFormat());
	const int	size				= texture.getLevelFace(levelNdx, coords.face).getWidth();

	const Vec2	uBounds				= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size, coords.s, prec.coordBits.x(), prec.uvwBits.x());
	const Vec2	vBounds				= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size, coords.t, prec.coordBits.y(), prec.uvwBits.y());

	// Integer coordinate bounds for (x0,y0) - without wrap mode
	const int	minI				= deFloorFloatToInt32(uBounds.x()-0.5f);
	const int	maxI				= deFloorFloatToInt32(uBounds.y()-0.5f);
	const int	minJ				= deFloorFloatToInt32(vBounds.x()-0.5f);
	const int	maxJ				= deFloorFloatToInt32(vBounds.y()-0.5f);

	// Face accesses
	ConstPixelBufferAccess faces[CUBEFACE_LAST];
	for (int face = 0; face < CUBEFACE_LAST; face++)
		faces[face] = texture.getLevelFace(levelNdx, CubeFace(face));

	for (int j = minJ; j <= maxJ; j++)
	{
		for (int i = minI; i <= maxI; i++)
		{
			const CubeFaceIntCoords	c00	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i+0, j+0)), size);
			const CubeFaceIntCoords	c10	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i+1, j+0)), size);
			const CubeFaceIntCoords	c01	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i+0, j+1)), size);
			const CubeFaceIntCoords	c11	= remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, IVec2(i+1, j+1)), size);

			// If any of samples is out of both edges, implementations can do pretty much anything according to spec.
			// \todo [2013-07-08 pyry] Test the special case where all corner pixels have exactly the same color.
			if (c00.face == CUBEFACE_LAST || c01.face == CUBEFACE_LAST || c10.face == CUBEFACE_LAST || c11.face == CUBEFACE_LAST)
				return true;

			// Bounds for filtering factors
			const float	minA	= de::clamp((uBounds.x()-0.5f)-float(i), 0.0f, 1.0f);
			const float	maxA	= de::clamp((uBounds.y()-0.5f)-float(i), 0.0f, 1.0f);
			const float	minB	= de::clamp((vBounds.x()-0.5f)-float(j), 0.0f, 1.0f);
			const float	maxB	= de::clamp((vBounds.y()-0.5f)-float(j), 0.0f, 1.0f);

			Vec4 depths;
			depths[0] = faces[c00.face].getPixDepth(c00.s, c00.t);
			depths[1] = faces[c10.face].getPixDepth(c10.s, c10.t);
			depths[2] = faces[c01.face].getPixDepth(c01.s, c01.t);
			depths[3] = faces[c11.face].getPixDepth(c11.s, c11.t);

			if (isBilinearCompareValid(sampler.compare, prec, depths, Vec2(minA, maxA), Vec2(minB, maxB), cmpReference, result, isFixedPointDepth))
				return true;
		}
	}

	return false;
}

static bool isCubeLevelCompareResultValid (const TextureCubeView&			texture,
										   const int						levelNdx,
										   const Sampler&					sampler,
										   const Sampler::FilterMode		filterMode,
										   const TexComparePrecision&		prec,
										   const CubeFaceFloatCoords&		coords,
										   const float						cmpReference,
										   const float						result)
{
	if (filterMode == Sampler::LINEAR)
	{
		if (sampler.seamlessCubeMap)
			return isSeamlessLinearCompareResultValid(texture, levelNdx, sampler, prec, coords, cmpReference, result);
		else
			return isLinearCompareResultValid(texture.getLevelFace(levelNdx, coords.face), sampler, prec, Vec2(coords.s, coords.t), 0, cmpReference, result);
	}
	else
		return isNearestCompareResultValid(texture.getLevelFace(levelNdx, coords.face), sampler, prec, Vec2(coords.s, coords.t), 0, cmpReference, result);
}

bool isTexCompareResultValid (const TextureCubeView& texture, const Sampler& sampler, const TexComparePrecision& prec, const Vec3& coord, const Vec2& lodBounds, const float cmpReference, const float result)
{
	int			numPossibleFaces				= 0;
	CubeFace	possibleFaces[CUBEFACE_LAST];

	DE_ASSERT(isSamplerSupported(sampler));

	getPossibleCubeFaces(coord, prec.coordBits, &possibleFaces[0], numPossibleFaces);

	if (numPossibleFaces == 0)
		return true; // Result is undefined.

	for (int tryFaceNdx = 0; tryFaceNdx < numPossibleFaces; tryFaceNdx++)
	{
		const CubeFaceFloatCoords	faceCoords		(possibleFaces[tryFaceNdx], projectToFace(possibleFaces[tryFaceNdx], coord));
		const float					minLod			= lodBounds.x();
		const float					maxLod			= lodBounds.y();
		const bool					canBeMagnified	= minLod <= sampler.lodThreshold;
		const bool					canBeMinified	= maxLod > sampler.lodThreshold;

		if (canBeMagnified)
		{
			if (isCubeLevelCompareResultValid(texture, 0, sampler, sampler.magFilter, prec, faceCoords, cmpReference, result))
				return true;
		}

		if (canBeMinified)
		{
			const bool	isNearestMipmap	= isNearestMipmapFilter(sampler.minFilter);
			const bool	isLinearMipmap	= isLinearMipmapFilter(sampler.minFilter);
			const int	minTexLevel		= 0;
			const int	maxTexLevel		= texture.getNumLevels()-1;

			DE_ASSERT(minTexLevel < maxTexLevel);

			if (isLinearMipmap)
			{
				const int		minLevel		= de::clamp((int)deFloatFloor(minLod), minTexLevel, maxTexLevel-1);
				const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod), minTexLevel, maxTexLevel-1);

				DE_ASSERT(minLevel <= maxLevel);

				for (int level = minLevel; level <= maxLevel; level++)
				{
					const float		minF	= de::clamp(minLod - float(level), 0.0f, 1.0f);
					const float		maxF	= de::clamp(maxLod - float(level), 0.0f, 1.0f);

					if (isCubeMipmapLinearCompareResultValid(texture, level, sampler, getLevelFilter(sampler.minFilter), prec, faceCoords, Vec2(minF, maxF), cmpReference, result))
						return true;
				}
			}
			else if (isNearestMipmap)
			{
				// \note The accurate formula for nearest mipmapping is level = ceil(lod + 0.5) - 1 but Khronos has made
				//		 decision to allow floor(lod + 0.5) as well.
				const int		minLevel		= de::clamp((int)deFloatCeil(minLod + 0.5f) - 1,	minTexLevel, maxTexLevel);
				const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod + 0.5f),		minTexLevel, maxTexLevel);

				DE_ASSERT(minLevel <= maxLevel);

				for (int level = minLevel; level <= maxLevel; level++)
				{
					if (isCubeLevelCompareResultValid(texture, level, sampler, getLevelFilter(sampler.minFilter), prec, faceCoords, cmpReference, result))
						return true;
				}
			}
			else
			{
				if (isCubeLevelCompareResultValid(texture, 0, sampler, sampler.minFilter, prec, faceCoords, cmpReference, result))
					return true;
			}
		}
	}

	return false;
}

bool isTexCompareResultValid (const Texture2DArrayView& texture, const Sampler& sampler, const TexComparePrecision& prec, const Vec3& coord, const Vec2& lodBounds, const float cmpReference, const float result)
{
	const float		depthErr	= computeFloatingPointError(coord.z(), prec.coordBits.z()) + computeFixedPointError(prec.uvwBits.z());
	const float		minZ		= coord.z()-depthErr;
	const float		maxZ		= coord.z()+depthErr;
	const int		minLayer	= de::clamp(deFloorFloatToInt32(minZ + 0.5f), 0, texture.getNumLayers()-1);
	const int		maxLayer	= de::clamp(deFloorFloatToInt32(maxZ + 0.5f), 0, texture.getNumLayers()-1);

	for (int layer = minLayer; layer <= maxLayer; layer++)
	{
		const float		minLod			= lodBounds.x();
		const float		maxLod			= lodBounds.y();
		const bool		canBeMagnified	= minLod <= sampler.lodThreshold;
		const bool		canBeMinified	= maxLod > sampler.lodThreshold;

		DE_ASSERT(isSamplerSupported(sampler));

		if (canBeMagnified)
		{
			if (isLevelCompareResultValid(texture.getLevel(0), sampler, sampler.magFilter, prec, coord.swizzle(0,1), layer, cmpReference, result))
				return true;
		}

		if (canBeMinified)
		{
			const bool	isNearestMipmap	= isNearestMipmapFilter(sampler.minFilter);
			const bool	isLinearMipmap	= isLinearMipmapFilter(sampler.minFilter);
			const int	minTexLevel		= 0;
			const int	maxTexLevel		= texture.getNumLevels()-1;

			DE_ASSERT(minTexLevel < maxTexLevel);

			if (isLinearMipmap)
			{
				const int		minLevel		= de::clamp((int)deFloatFloor(minLod), minTexLevel, maxTexLevel-1);
				const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod), minTexLevel, maxTexLevel-1);

				DE_ASSERT(minLevel <= maxLevel);

				for (int level = minLevel; level <= maxLevel; level++)
				{
					const float		minF	= de::clamp(minLod - float(level), 0.0f, 1.0f);
					const float		maxF	= de::clamp(maxLod - float(level), 0.0f, 1.0f);

					if (isMipmapLinearCompareResultValid(texture.getLevel(level), texture.getLevel(level+1), sampler, getLevelFilter(sampler.minFilter), prec, coord.swizzle(0,1), layer, Vec2(minF, maxF), cmpReference, result))
						return true;
				}
			}
			else if (isNearestMipmap)
			{
				// \note The accurate formula for nearest mipmapping is level = ceil(lod + 0.5) - 1 but Khronos has made
				//		 decision to allow floor(lod + 0.5) as well.
				const int		minLevel		= de::clamp((int)deFloatCeil(minLod + 0.5f) - 1,	minTexLevel, maxTexLevel);
				const int		maxLevel		= de::clamp((int)deFloatFloor(maxLod + 0.5f),		minTexLevel, maxTexLevel);

				DE_ASSERT(minLevel <= maxLevel);

				for (int level = minLevel; level <= maxLevel; level++)
				{
					if (isLevelCompareResultValid(texture.getLevel(level), sampler, getLevelFilter(sampler.minFilter), prec, coord.swizzle(0,1), layer, cmpReference, result))
						return true;
				}
			}
			else
			{
				if (isLevelCompareResultValid(texture.getLevel(0), sampler, sampler.minFilter, prec, coord.swizzle(0,1), layer, cmpReference, result))
					return true;
			}
		}
	}

	return false;
}

static bool isGatherOffsetsCompareResultValid (const ConstPixelBufferAccess&	texture,
											   const Sampler&					sampler,
											   const TexComparePrecision&		prec,
											   const Vec2&						coord,
											   int								coordZ,
											   const IVec2						(&offsets)[4],
											   float							cmpReference,
											   const Vec4&						result)
{
	const bool	isFixedPointDepth	= isFixedPointDepthTextureFormat(texture.getFormat());
	const Vec2	uBounds				= computeNonNormalizedCoordBounds(sampler.normalizedCoords, texture.getWidth(),		coord.x(), prec.coordBits.x(), prec.uvwBits.x());
	const Vec2	vBounds				= computeNonNormalizedCoordBounds(sampler.normalizedCoords, texture.getHeight(),	coord.y(), prec.coordBits.y(), prec.uvwBits.y());

	// Integer coordinate bounds for (x0, y0) - without wrap mode
	const int	minI				= deFloorFloatToInt32(uBounds.x()-0.5f);
	const int	maxI				= deFloorFloatToInt32(uBounds.y()-0.5f);
	const int	minJ				= deFloorFloatToInt32(vBounds.x()-0.5f);
	const int	maxJ				= deFloorFloatToInt32(vBounds.y()-0.5f);

	const int	w					= texture.getWidth();
	const int	h					= texture.getHeight();

	for (int j = minJ; j <= maxJ; j++)
	{
		for (int i = minI; i <= maxI; i++)
		{
			bool isCurrentPixelValid = true;

			for (int offNdx = 0; offNdx < 4 && isCurrentPixelValid; offNdx++)
			{
				// offNdx-th coordinate offset and then wrapped.
				const int			x		= wrap(sampler.wrapS, i+offsets[offNdx].x(), w);
				const int			y		= wrap(sampler.wrapT, j+offsets[offNdx].y(), h);
				const float			depth	= texture.getPixDepth(x, y, coordZ);
				const CmpResultSet	resSet	= execCompare(sampler.compare, depth, cmpReference, prec.referenceBits, isFixedPointDepth);

				if (!isResultInSet(resSet, result[offNdx], prec.resultBits))
					isCurrentPixelValid = false;
			}

			if (isCurrentPixelValid)
				return true;
		}
	}

	return false;
}

bool isGatherOffsetsCompareResultValid (const Texture2DView&		texture,
										const Sampler&				sampler,
										const TexComparePrecision&	prec,
										const Vec2&					coord,
										const IVec2					(&offsets)[4],
										float						cmpReference,
										const Vec4&					result)
{
	return isGatherOffsetsCompareResultValid(texture.getLevel(0), sampler, prec, coord, 0, offsets, cmpReference, result);
}

bool isGatherOffsetsCompareResultValid (const Texture2DArrayView&	texture,
										const Sampler&				sampler,
										const TexComparePrecision&	prec,
										const Vec3&					coord,
										const IVec2					(&offsets)[4],
										float						cmpReference,
										const Vec4&					result)
{
	const float		depthErr	= computeFloatingPointError(coord.z(), prec.coordBits.z()) + computeFixedPointError(prec.uvwBits.z());
	const float		minZ		= coord.z()-depthErr;
	const float		maxZ		= coord.z()+depthErr;
	const int		minLayer	= de::clamp(deFloorFloatToInt32(minZ + 0.5f), 0, texture.getNumLayers()-1);
	const int		maxLayer	= de::clamp(deFloorFloatToInt32(maxZ + 0.5f), 0, texture.getNumLayers()-1);

	for (int layer = minLayer; layer <= maxLayer; layer++)
	{
		if (isGatherOffsetsCompareResultValid(texture.getLevel(0), sampler, prec, coord.swizzle(0,1), layer, offsets, cmpReference, result))
			return true;
	}
	return false;
}

static bool isGatherCompareResultValid (const TextureCubeView&		texture,
										const Sampler&				sampler,
										const TexComparePrecision&	prec,
										const CubeFaceFloatCoords&	coords,
										float						cmpReference,
										const Vec4&					result)
{
	const bool	isFixedPointDepth	= isFixedPointDepthTextureFormat(texture.getLevelFace(0, coords.face).getFormat());
	const int	size				= texture.getLevelFace(0, coords.face).getWidth();
	const Vec2	uBounds				= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size, coords.s, prec.coordBits.x(), prec.uvwBits.x());
	const Vec2	vBounds				= computeNonNormalizedCoordBounds(sampler.normalizedCoords, size, coords.t, prec.coordBits.y(), prec.uvwBits.y());

	// Integer coordinate bounds for (x0,y0) - without wrap mode
	const int	minI				= deFloorFloatToInt32(uBounds.x()-0.5f);
	const int	maxI				= deFloorFloatToInt32(uBounds.y()-0.5f);
	const int	minJ				= deFloorFloatToInt32(vBounds.x()-0.5f);
	const int	maxJ				= deFloorFloatToInt32(vBounds.y()-0.5f);

	// Face accesses
	ConstPixelBufferAccess faces[CUBEFACE_LAST];
	for (int face = 0; face < CUBEFACE_LAST; face++)
		faces[face] = texture.getLevelFace(0, CubeFace(face));

	for (int j = minJ; j <= maxJ; j++)
	{
		for (int i = minI; i <= maxI; i++)
		{
			static const IVec2 offsets[4] =
			{
				IVec2(0, 1),
				IVec2(1, 1),
				IVec2(1, 0),
				IVec2(0, 0)
			};

			bool isCurrentPixelValid = true;

			for (int offNdx = 0; offNdx < 4 && isCurrentPixelValid; offNdx++)
			{
				const CubeFaceIntCoords c = remapCubeEdgeCoords(CubeFaceIntCoords(coords.face, i+offsets[offNdx].x(), j+offsets[offNdx].y()), size);
				// If any of samples is out of both edges, implementations can do pretty much anything according to spec.
				// \todo [2014-06-05 nuutti] Test the special case where all corner pixels have exactly the same color.
				//							 See also isSeamlessLinearCompareResultValid and similar.
				if (c.face == CUBEFACE_LAST)
					return true;

				const float			depth	= faces[c.face].getPixDepth(c.s, c.t);
				const CmpResultSet	resSet	= execCompare(sampler.compare, depth, cmpReference, prec.referenceBits, isFixedPointDepth);

				if (!isResultInSet(resSet, result[offNdx], prec.resultBits))
					isCurrentPixelValid = false;
			}

			if (isCurrentPixelValid)
				return true;
		}
	}

	return false;
}

bool isGatherCompareResultValid (const TextureCubeView&			texture,
								 const Sampler&					sampler,
								 const TexComparePrecision&		prec,
								 const Vec3&					coord,
								 float							cmpReference,
								 const Vec4&					result)
{
	int			numPossibleFaces				= 0;
	CubeFace	possibleFaces[CUBEFACE_LAST];

	getPossibleCubeFaces(coord, prec.coordBits, &possibleFaces[0], numPossibleFaces);

	if (numPossibleFaces == 0)
		return true; // Result is undefined.

	for (int tryFaceNdx = 0; tryFaceNdx < numPossibleFaces; tryFaceNdx++)
	{
		const CubeFaceFloatCoords faceCoords(possibleFaces[tryFaceNdx], projectToFace(possibleFaces[tryFaceNdx], coord));

		if (isGatherCompareResultValid(texture, sampler, prec, faceCoords, cmpReference, result))
			return true;
	}

	return false;
}

} // tcu
